/*
 * Classe do PEER EDGE 
 */
package trabalho3sd;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.URI;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.jxta.discovery.DiscoveryEvent;
import net.jxta.discovery.DiscoveryListener;
import net.jxta.discovery.DiscoveryService;
import net.jxta.document.Advertisement;
import net.jxta.document.AdvertisementFactory;
import net.jxta.endpoint.Message;
import net.jxta.endpoint.StringMessageElement;
import net.jxta.exception.PeerGroupException;
import net.jxta.id.IDFactory;
import net.jxta.peer.PeerID;
import net.jxta.peergroup.PeerGroup;
import net.jxta.peergroup.PeerGroupID;
import net.jxta.pipe.PipeID;
import net.jxta.pipe.PipeMsgEvent;
import net.jxta.pipe.PipeMsgListener;
import net.jxta.pipe.PipeService;
import net.jxta.platform.NetworkConfigurator;
import net.jxta.platform.NetworkManager;
import net.jxta.protocol.DiscoveryResponseMsg;
import net.jxta.protocol.PipeAdvertisement;
import net.jxta.socket.JxtaServerSocket;
import net.jxta.socket.JxtaSocket;
import net.jxta.util.JxtaBiDiPipe;

/*
 * @author Gustavo Luvizotto Cesar - 6783544 - gustavoluvizotto@gmail.com
 * @author Leonardo Lourenço Crespilho - 5124310 - lcrespilho@gmail.com
 */
public class Edge2 implements PipeMsgListener, DiscoveryListener {

    private static String Name;
    private static PeerID localPeerID;
    private static String SeedRDV;
    private static File ConfigurationFile;
    private static JxtaBiDiPipe MyBiDiPipeWithRDV;
    private static PeerGroup NetPeerGroup;
    private String msgFileName;

    public Edge2(String FileName) {
        this.msgFileName = FileName;
    }

    public Edge2() {
    }

    public PipeAdvertisement getSocketAdvertisement() {
        // Creating a Socket (Pipe) Advertisement
        PipeAdvertisement MyPipeAdvertisement = (PipeAdvertisement) AdvertisementFactory.newAdvertisement(PipeAdvertisement.getAdvertisementType());
        PipeID MyPipeID = IDFactory.newPipeID(PeerGroupID.defaultNetPeerGroupID, Name.getBytes());
        MyPipeAdvertisement.setPipeID(MyPipeID);
        MyPipeAdvertisement.setType(PipeService.UnicastType);
        MyPipeAdvertisement.setName("Advertisement do Socket");
        MyPipeAdvertisement.setDescription(msgFileName);
        return MyPipeAdvertisement;
    }

    /*
     * Nesse discoveryEvent, capturo os advertisements publicados e verifico se possuem o nome "Advertisement do Socket" e a descrição com nome do arquivo que desejo. Assim, pegamos os Advivertisements
     * provindos da publicação da criação do socket.
     * Esse evento é usado para a TAG "DOWNLOAD". (recebimento de arquivos).
     */
    @Override
    public void discoveryEvent(DiscoveryEvent TheDiscoveryEvent) {
        try {
            System.out.println("ENTROU NO LISTENER DE DiscoveryEvent");
            System.out.flush();

            DiscoveryResponseMsg TheDiscoveryResponseMsg = TheDiscoveryEvent.getResponse();

            if (TheDiscoveryResponseMsg != null) {
                System.out.println("Número de respostas: " + TheDiscoveryResponseMsg.getResponseCount());
                System.out.flush();
                Enumeration<Advertisement> TheEnumeration = TheDiscoveryResponseMsg.getAdvertisements();

                while (TheEnumeration.hasMoreElements()) {
                    PipeAdvertisement SocketADV = (PipeAdvertisement) TheEnumeration.nextElement();

                    System.out.println("Advertisement:\n" + SocketADV.toString() + "\n\n\n");
                    System.out.flush();

                    if (SocketADV.getName().toString().equals("Advertisement do Socket") && SocketADV.getDescription().toString().equals(msgFileName)) {

                        System.out.println("ACHOU O ADVERTISEMENT!!!:\n" + SocketADV.toString() + "\n\n\n");
                        System.out.flush();

                        JxtaSocket MySocket = new JxtaSocket(NetPeerGroup, SocketADV, 30000); //conectou no socket

                        // recebendo o arquivo
                        InputStream is = MySocket.getInputStream();
                        int filesize = 1048576; //1MB de tamanho máximo para o arquivo recebido
                        int bytesRead;
                        int current;
                        byte[] mybytearray = new byte[filesize];
                        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("/tmp/JXTA2/" + msgFileName));
                        bytesRead = is.read(mybytearray, 0, mybytearray.length);
                        current = bytesRead;
                        do {
                            bytesRead = is.read(mybytearray, current, (mybytearray.length - current));
                            if (bytesRead >= 0) {
                                current += bytesRead;
                            }
                        } while (bytesRead > -1);
                        bos.write(mybytearray, 0, current);
                        bos.flush();
                        bos.close(); // transferência do arquivo completa
                        System.out.println("Transferência do arquivo completada.");
                        break;
                    }
                }
            }
        } catch (IOException ex) {
            Logger.getLogger(Edge2.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /*
     * Chegou uma mensagem. Ela pode ser dos seguintes tipos:
     * - DELETAR: peer deve deletar o arquivo que a mensagem especifica
     * - ERRO: peer não obteve sucesso em sua operação de ACESSAR
     */
    @Override
    public void pipeMsgEvent(PipeMsgEvent PME) {
        Message ReceivedMessage = PME.getMessage();
        String msgType = ReceivedMessage.getMessageElement("TYPE").toString(); //pode ser publicar ou acessar
        msgFileName = ReceivedMessage.getMessageElement("FILENAME").toString(); //nome do arquivo a ser publicado ou acessado/baixado
        String msgPeerID = ReceivedMessage.getMessageElement("PEERID").toString();
        try {
            // peer apenas deleta de seu repositório.
            switch (msgType) {

                case "DELETAR": {
                    File[] files = new File("/tmp/JXTA2").listFiles();
                    for (File file : files) {
                        if (file.getName().equals(msgFileName)) {
                            file.delete();
                            break;
                        }
                    }
                    break;
                }

                case "ACESSOERRO": {    // erro ao acessar o arquivo. Arquivo não existe
                    System.out.println("Erro na operação de ACESSAR. Arquivo não existe ou não está disponível para download.");
                    break;
                }

                // TAG de concessão do pedido para acessar um arquivo em outro peer, ou seja, existe o arquivo requisitado em algum peer. Como o arquivo existe e o socket já está aberto, então inicia-se o download do mesmo    
                case "DOWNLOAD": {
                    if (msgPeerID.equals(localPeerID.toString())) {     // verifico se o arquivo é meu
                        System.out.println("Sucesso! Arquivo disponível localmente");
                    } else {        // arquivo localizado em outro peer. Criar conexão por socket com o outro peer e enviar o pedido de arquivo.
                        NetPeerGroup.getDiscoveryService().getRemoteAdvertisements(null, DiscoveryService.ADV, null, null, 20, new Edge2(msgFileName));
                    }
                    break;
                }

                case "ABRESOCKET": {
                    JxtaServerSocket MyJXTAServerSocket = new JxtaServerSocket(NetPeerGroup, getSocketAdvertisement(), 20, 30000);
                    NetPeerGroup.getDiscoveryService().publish(getSocketAdvertisement(), 30000, 30000);      // duraçao da publicação desse advertisemente de 30s (suficiente para criar o socket).
                    NetPeerGroup.getDiscoveryService().remotePublish(getSocketAdvertisement(), 30000);      // duraçao da publicação desse advertisemente de 30s (suficiente para criar o socket).
                    System.out.println("Antes do accept()");
                    System.out.flush();
                    Socket MySocket = MyJXTAServerSocket.accept(); // timeout = 30s, vindo da linha anterior
                    System.out.println("Depois do accept()");
                    System.out.flush();
                    if (MySocket != null) {
                        File file = new File("/tmp/JXTA2/" + msgFileName);
                        OutputStream os = MySocket.getOutputStream();
                        byte[] mybytearray = new byte[(int) file.length() + 1];
                        FileInputStream fis = new FileInputStream(file);
                        BufferedInputStream bis = new BufferedInputStream(fis);
                        bis.read(mybytearray, 0, mybytearray.length);
                        os.write(mybytearray, 0, mybytearray.length);
                        os.flush();
                        MySocket.close();
                    }
                }
            }
        } catch (IOException ex) {
            Logger.getLogger(Edge2.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public static void main(String[] args) {
        try {
            inicializaAtributos(); //seta os atributos da classe
            NetworkManager.RecursiveDelete(ConfigurationFile);
            NetworkManager MyNetworkManager = new NetworkManager(NetworkManager.ConfigMode.EDGE, Name, ConfigurationFile.toURI());
            NetworkConfigurator MyNetworkConfigurator = MyNetworkManager.getConfigurator();
            MyNetworkConfigurator.clearRendezvousSeeds();
            MyNetworkConfigurator.addSeedRendezvous(URI.create(SeedRDV));
            MyNetworkConfigurator.setTcpEnabled(true);
            MyNetworkConfigurator.setTcpIncoming(true);
            MyNetworkConfigurator.setTcpOutgoing(true);
            MyNetworkConfigurator.setUseMulticast(false);
            MyNetworkConfigurator.setPeerID(localPeerID);
            NetPeerGroup = MyNetworkManager.startNetwork();
            NetPeerGroup.getRendezVousService().setAutoStart(false);

            MyNetworkManager.waitForRendezvousConnection(5000);  //tenta conectar no RDV por 5s
            MyBiDiPipeWithRDV = new JxtaBiDiPipe(NetPeerGroup, Rendezvous.getPipeAdvertisement(), 30000, new Edge2()); // Criando o BiDiPipe e setando o Listener
            if (MyBiDiPipeWithRDV.isBound()) {

                // interface com o usuario (util para sincronizar os codigos/peers)
                int op;
                String nomeArquivo;
                do {
//                    System.out.println("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
                    System.out.println("\n\n");
                    System.out.println("Escolha a opção desejada");
                    System.out.println("[1] Publica todos arquivos locais");
                    System.out.println("[2] Publica um arquivo em específico");
                    System.out.println("[3] Acessa/Baixa um arquivo");
                    System.out.println("[4] Deleta um arquivo");
                    System.out.println("[5] Listar metadados");
                    System.out.println("[6] Sair");
                    System.out.println("Opção: ");
                    Scanner read = new Scanner(System.in);
                    op = read.nextInt();
                    read.nextLine();
                    switch (op) {
                        case 1: {
                            publicaArquivosLocais();
                            break;
                        }
                        case 2: {
                            System.out.println("Digite o nome do arquivo: ");
                            nomeArquivo = read.nextLine();
                            publicaArquivo(nomeArquivo);
                            break;
                        }
                        case 3: {
                            System.out.println("Digite o nome do arquivo: ");
                            nomeArquivo = read.nextLine();
                            acessaArquivo(nomeArquivo);
                            break;
                        }
                        case 4: {
                            System.out.println("Digite o nome do arquivo: ");
                            nomeArquivo = read.nextLine();
                            deletaArquivo(nomeArquivo);
                            break;
                        }
                        case 5: {
                            listarMetadados();
                            break;
                        }
                    }
                } while (op != 6);
            }

            // Para deletar os metadados desse Peer após um exit com sucesso. Não trata o caso do processo ter recebido um kill, nem morte súbita.
            File[] FileList = new File("/tmp/JXTA2").listFiles();
            for (File file : FileList) {
                deletaMetadado(file);
            }

            MyBiDiPipeWithRDV.close();
            MyNetworkManager.stopNetwork();
        } catch (PeerGroupException | IOException ex) {
            Logger.getLogger(Edge2.class.getName()).log(Level.SEVERE, null, ex);
        }
        System.exit(0);
    }

    static void inicializaAtributos() {
        try {
            Name = InetAddress.getLocalHost().getHostName();
            Name = "Edge2";
            localPeerID = IDFactory.newPeerID(PeerGroupID.defaultNetPeerGroupID, Name.getBytes());
            SeedRDV = "tcp://127.0.0.1:" + 9722;
            ConfigurationFile = new File("." + System.getProperty("file.separator") + Name);   // arquivos de configuração da rede   
        } catch (UnknownHostException ex) {
            Logger.getLogger(Edge2.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    static void publicaArquivosLocais() {
        File[] FileList = new File("/tmp/JXTA2").listFiles();
        for (File file : FileList) {
            publicaArquivo(file);
        }
    }

    static void publicaArquivo(File file) {
        try {
            Message MyMessage = new Message();
            MyMessage.addMessageElement(new StringMessageElement("TYPE", "PUBLICAR", null));
            MyMessage.addMessageElement(new StringMessageElement("FILENAME", file.getName(), null));
            MyMessage.addMessageElement(new StringMessageElement("LASTMODIFIED", String.valueOf(file.lastModified()), null));
            MyBiDiPipeWithRDV.sendMessage(MyMessage);
        } catch (IOException ex) {
            Logger.getLogger(Edge2.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    static void publicaArquivo(String fileName) {
        try {
            File file = new File("/tmp/JXTA2/" + fileName);
            Message MyMessage = new Message();
            MyMessage.addMessageElement(new StringMessageElement("TYPE", "PUBLICAR", null));
            MyMessage.addMessageElement(new StringMessageElement("FILENAME", file.getName(), null));
            MyMessage.addMessageElement(new StringMessageElement("LASTMODIFIED", String.valueOf(file.lastModified()), null));
            MyBiDiPipeWithRDV.sendMessage(MyMessage);
        } catch (IOException ex) {
            Logger.getLogger(Edge2.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    // esse cara deleta o arquivo. manda uma mensagem para o RDV para que isso seja feito. Se for local, o RDV vai devolver uma msg, senão ele envia a msg para o peer correspondente que precisa deletar o arquivo
    static void deletaArquivo(String fileName) {
        try {
            Message MyMessage = new Message();
            MyMessage.addMessageElement(new StringMessageElement("TYPE", "DELETAR", null));
            MyMessage.addMessageElement(new StringMessageElement("FILENAME", fileName, null));
            MyMessage.addMessageElement(new StringMessageElement("LASTMODIFIED", "123", null)); //lixo
            MyBiDiPipeWithRDV.sendMessage(MyMessage);
        } catch (IOException ex) {
            Logger.getLogger(Edge2.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    // esse cara deleta o arquivo. manda uma mensagem para o RDV para que isso seja feito. Se for local, o RDV vai devolver uma msg, senão ele envia a msg para o peer correspondente que precisa deletar o arquivo
    static void deletaMetadado(File file) {
        try {
            Message MyMessage = new Message();
            MyMessage.addMessageElement(new StringMessageElement("TYPE", "DELETAMETADADO", null));
            MyMessage.addMessageElement(new StringMessageElement("FILENAME", file.getName(), null));
            MyMessage.addMessageElement(new StringMessageElement("LASTMODIFIED", "123", null)); //lixo
            MyBiDiPipeWithRDV.sendMessage(MyMessage);
        } catch (IOException ex) {
            Logger.getLogger(Edge2.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    static void acessaArquivo(String fileName) {
        try {
            Message MyMessage = new Message();
            MyMessage.addMessageElement(new StringMessageElement("TYPE", "ACESSAR", null));
            MyMessage.addMessageElement(new StringMessageElement("FILENAME", fileName, null));
            MyMessage.addMessageElement(new StringMessageElement("LASTMODIFIED", "123", null)); //lixo
            MyBiDiPipeWithRDV.sendMessage(MyMessage);

            System.out.flush();
            Thread.sleep(2000);  //após alguns segundos (digamos 2s) o arquivo estará com este peer.
            System.out.println("Você tem 5 segundos para alterar o arquivo");
            System.out.flush();
            Thread.sleep(5000); //espera 30 segundos pra dar tempo de alterar o arquivo localmente.
            publicaArquivo(fileName); //após modificação local, publica o arquivo novamente.
        } catch (InterruptedException | IOException ex) {
            Logger.getLogger(Edge2.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    static void listarMetadados() {
        try {
            Message MyMessage = new Message();
            MyMessage.addMessageElement(new StringMessageElement("TYPE", "LISTARMETADADOS", null));
            MyMessage.addMessageElement(new StringMessageElement("FILENAME", "lixo", null));
            MyMessage.addMessageElement(new StringMessageElement("LASTMODIFIED", "123", null)); //lixo
            MyBiDiPipeWithRDV.sendMessage(MyMessage);
        } catch (IOException ex) {
            Logger.getLogger(Edge2.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}
